package sh.grapendaal.tsuushin.module.uno;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import sh.grapendaal.tsuushin.message.PrivateMessage;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Card;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Playable;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Player;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Stack;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Card.Rank;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Card.Suit;
import sh.grapendaal.tsuushin.module.uno.engine.Input;
import sh.grapendaal.tsuushin.module.uno.engine.UnoException;

public class IrcInput extends Input {
	private enum Command {
		JOIN, LEAVE, START, STOP, PLAY, DRAW, PASS, INFO
	}
	
	private static final Pattern COMMAND_SPLIT_PATTERN = Pattern.compile("\\s+");
	
	private static final Map<String, Command> COMMAND_MAP = new HashMap<String, Command>();
	static {
		COMMAND_MAP.put("jo", Command.JOIN);
		COMMAND_MAP.put("join", Command.JOIN);
		COMMAND_MAP.put("le", Command.LEAVE);
		COMMAND_MAP.put("leave", Command.LEAVE);
		COMMAND_MAP.put("st", Command.START);
		COMMAND_MAP.put("start", Command.START);
		COMMAND_MAP.put("stop", Command.STOP);
		COMMAND_MAP.put("pl", Command.PLAY);
		COMMAND_MAP.put("play", Command.PLAY);
		COMMAND_MAP.put("dr", Command.DRAW);
		COMMAND_MAP.put("draw", Command.DRAW);
		COMMAND_MAP.put("pa", Command.PASS);
		COMMAND_MAP.put("pass", Command.PASS);
		COMMAND_MAP.put("ca", Command.INFO);
		COMMAND_MAP.put("cards", Command.INFO);
	}
	
	private static final Map<String, Suit> CARD_SUIT_MAP = new HashMap<String, Suit>();
	static {
		CARD_SUIT_MAP.put("w", Suit.WILDCARD);
		CARD_SUIT_MAP.put("b", Suit.BLUE);
		CARD_SUIT_MAP.put("g", Suit.GREEN);
		CARD_SUIT_MAP.put("r", Suit.RED);
		CARD_SUIT_MAP.put("y", Suit.YELLOW);
	}
	
	private static final Map<String, Rank> CARD_RANK_MAP = new HashMap<String, Rank>();
	static {
		CARD_RANK_MAP.put("", Rank.NONE);
		CARD_RANK_MAP.put("0", Rank.ZERO);
		CARD_RANK_MAP.put("1", Rank.ONE);
		CARD_RANK_MAP.put("2", Rank.TWO);
		CARD_RANK_MAP.put("3", Rank.THREE);
		CARD_RANK_MAP.put("4", Rank.FOUR);
		CARD_RANK_MAP.put("5", Rank.FIVE);
		CARD_RANK_MAP.put("6", Rank.SIX);
		CARD_RANK_MAP.put("7", Rank.SEVEN);
		CARD_RANK_MAP.put("8", Rank.EIGHT);
		CARD_RANK_MAP.put("9", Rank.NINE);
		CARD_RANK_MAP.put("d2", Rank.DRAW_TWO);
		CARD_RANK_MAP.put("d4", Rank.DRAW_FOUR);
		CARD_RANK_MAP.put("r", Rank.REVERSE);
		CARD_RANK_MAP.put("s", Rank.SKIP);
	}
	
	private static final Set<Command> VALID_PENDING_COMMANDS = new HashSet<Command>();
	static {
		VALID_PENDING_COMMANDS.add(Command.JOIN);
		VALID_PENDING_COMMANDS.add(Command.LEAVE);
		VALID_PENDING_COMMANDS.add(Command.START);
		VALID_PENDING_COMMANDS.add(Command.STOP);
	}
	
	private static final Set<Command> VALID_PLAYING_COMMANDS = new HashSet<Command>();
	static {
		VALID_PLAYING_COMMANDS.add(Command.STOP);
		VALID_PLAYING_COMMANDS.add(Command.PLAY);
		VALID_PLAYING_COMMANDS.add(Command.DRAW);
		VALID_PLAYING_COMMANDS.add(Command.PASS);
		VALID_PLAYING_COMMANDS.add(Command.INFO);
	}

	private char prefix;
	
	public IrcInput(char prefix) {
		this.prefix = prefix;
	}

	@Override
	public Playable parseParameterInput(String originalInput) {
		String[] chunks = sanitizeAndSplitMessage(originalInput);
		if (chunks.length == 1) {
			// Syntax: !play [suit][rank]
			String suit = Character.toString(chunks[0].charAt(0));
			String rank = chunks[0].substring(1);
			
			validateSuit(suit);
			validateRank(rank);
			
			if (CARD_SUIT_MAP.get(suit) == Suit.WILDCARD) {
				throw new UnoException("You must supply a color to change to.");
			}
			
			return new Card(CARD_SUIT_MAP.get(suit), CARD_RANK_MAP.get(rank));
		} else if (chunks.length == 2) {
			// Syntax: !play w[rank] [nextSuit]
			String suit = Character.toString(chunks[0].charAt(0));
			String rank = chunks[0].substring(1);
			String nextSuit = chunks[1];
			
			validateSuit(suit);
			validateRank(rank);
			validateSuit(nextSuit);
			
			if (CARD_SUIT_MAP.get(suit) != Suit.WILDCARD) {
				throw new UnoException("Invalid syntax used.");
			}
			
			Card card = new Card(CARD_SUIT_MAP.get(suit), CARD_RANK_MAP.get(rank));
			card.setNextSuit(CARD_SUIT_MAP.get(nextSuit));
			return card;
		} else if (chunks.length == 3 && ("s".equals(chunks[0]) || "stack".equals(chunks[0]))) {
			// Syntax: !play s [rank] [endSuit]
			String rank = chunks[1];
			String endSuit = chunks[2];
			
			validateRank(rank);
			validateSuit(endSuit);
			
			return new Stack(CARD_RANK_MAP.get(rank), CARD_SUIT_MAP.get(endSuit));
		} else {
			throw new UnoException("Invalid syntax used.");
		}
	}

	private void validateSuit(String suit) {
		if (!CARD_SUIT_MAP.containsKey(suit)) {
			throw new UnoException("Invalid suit supplied.");
		}
	}
	
	private void validateRank(String rank) {
		if (!CARD_RANK_MAP.containsKey(rank)) {
			throw new UnoException("Invalid rank supplied.");
		}
	}

	public void processMessage(PrivateMessage incoming) {
		Command command = determineCommand(this.prefix, incoming.getContent());
		if (command == null) {
			return;
		}
		
		switch (getEngine().getState()) {
		case IDLE:
			processIdleCommand(command, incoming);
			break;
			
		case PENDING:
			processPendingCommand(command, incoming);
			break;
			
		case PLAYING:
			processPlayingCommand(command, incoming);
			break;
		}
	}

	private void processIdleCommand(Command command, PrivateMessage incoming) {
		switch (command) {
		case JOIN:
			dispatchPlayerJoin(new Player(incoming.getUser().getNickname()));
			break;
		}
	}

	private void processPendingCommand(Command command, PrivateMessage incoming) {
		if (!VALID_PENDING_COMMANDS.contains(command)) {
			return;
		}
		
		Player player = null;
		if (command != Command.JOIN) {
			try {
				player = getEngine().getPlayerService().getPlayerByNickname(incoming.getUser().getNickname());
			} catch (UnoException e) {
				return;
			}
		}
		
		switch (command) {
		case JOIN:
			dispatchPlayerJoin(new Player(incoming.getUser().getNickname()));
			break;
			
		case LEAVE:
			dispatchPlayerLeave(player);
			break;
			
		case START:
			dispatchGameStart(player);
			break;
		
		case STOP:
			dispatchGameStop(player);
			break;
		}
	}

	private void processPlayingCommand(Command command, PrivateMessage incoming) {
		if (!VALID_PLAYING_COMMANDS.contains(command)) {
			return;
		}
		
		Player player;
		try {
			player = getEngine().getPlayerService().getPlayerByNickname(incoming.getUser().getNickname());
		} catch (UnoException e) {
			return;
		}
		
		switch (command) {
		case STOP:
			dispatchGameStop(player);
			break;
			
		case PLAY:
			String[] chunks = sanitizeAndSplitMessage(incoming.getContent(), 2);
			Playable input;
			try {
				input = parseParameterInput(chunks[1]);
			} catch (UnoException e) {
				getEngine().getOutput().showError(e.getMessage());
				break;
			}
			dispatchCardPlay(player, input);
			break;
			
		case DRAW:
			dispatchCardDraw(player);
			break;
			
		case PASS:
			dispatchCardPass(player);
			break;
			
		case INFO:
			// TODO
			break;
		}
	}

	private static Command determineCommand(char prefix, String content) {
		String[] chunks = sanitizeAndSplitMessage(content);
		String trigger = null;
		if (chunks[0].length() == 2) {
			trigger = chunks[0];
		} else if (chunks[0].charAt(0) == prefix) {
			trigger = chunks[0].substring(1);
		}
		
		if (trigger != null && COMMAND_MAP.containsKey(trigger)) {
			return COMMAND_MAP.get(trigger);
		} else {
			return null;
		}
	}
	
	private static String[] sanitizeAndSplitMessage(String message) {
		return sanitizeAndSplitMessage(message, 0);
	}
	
	private static String[] sanitizeAndSplitMessage(String message, int limit) {
		return COMMAND_SPLIT_PATTERN.split(message.trim().toLowerCase(UnoConstants.DEFAULT_LOCALE), limit);
	}

	public static boolean containsGameCreateCommand(char prefix, String content) {
		return determineCommand(prefix, content) == Command.JOIN;
	}

	public static boolean containsGameStopCommand(char prefix, String content) {
		return determineCommand(prefix, content) == Command.STOP;
	}
}
